﻿using System;
using System.Web.SessionState;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.ComponentModel;
using System.Drawing;
using System.Drawing.Text;
using System.Text;
using System.Security.Cryptography;

namespace Hxsoft.Web.UI.WebControls
{
	/// <summary>
	/// 验证码自定义控件。
	/// </summary>
	[ToolboxData("<{0}:CheckCode runat=server></{0}:CheckCode>")]
	public class CheckCode : System.Web.UI.Control
	{
		/// <summary>
		/// 生成验证码的存储键。
		/// </summary>
		public static readonly string StorageKey = "Hxsoft.Web.UI.WebControls.CheckCode.StorageKey";

		/// <summary>
		/// 验证码的内补丁。
		/// </summary>
		[Browsable(true), Category("Appearance"), DefaultValue(0), Description("定义验证码框的内补丁。")]
		public int Padding
		{
			get
			{
				object o = ViewState["Padding"];
				return o == null ? 0 : (int)o;
			}
			set
			{
				ViewState["Padding"] = value;
			}
		}

		/// <summary>
		/// 验证码的字体。
		/// </summary>
		[Browsable(true), Category("Appearance"), Description("字体设置。")]
		public Font Font
		{
			get
			{
				if (ViewState["Font"] == null)
					ViewState["Font"] = new Font(FontFamily.GenericSansSerif, 18, GraphicsUnit.Pixel);
				return (Font)ViewState["Font"];
			}
			set
			{
				ViewState["Font"] = value;
			}
		}

		/// <summary>
		/// 验证码的长度。
		/// </summary>
		[Browsable(true), Category("Appearance"), DefaultValue(4), Description("定义验证码的长度。")]
		public int CheckCodeLength
		{
			get
			{
				object o = ViewState["CheckCodeLength"];
				return o == null ? 4 : (int)o;
			}
			set
			{
				ViewState["CheckCodeLength"] = value;
			}
		}

		/// <summary>
		/// 验证码的宽度。
		/// </summary>
		[Browsable(true), Category("Layout"), Description("定义宽度。")]
		public int? Width
		{
			get
			{
				return (int?)ViewState["Width"];
			}
			set
			{
				ViewState["Width"] = value;
			}
		}

		/// <summary>
		/// 验证码的高度。
		/// </summary>
		[Browsable(true), Category("Layout"), Description("定义高度。")]
		public int? Height
		{
			get
			{
				return (int?)ViewState["Height"];
			}
			set
			{
				ViewState["Height"] = value;
			}
		}

		/// <summary>
		/// 验证码中字符的偏移量。
		/// </summary>
		public int CharFloatAmount
		{
			get
			{
				object o = ViewState["CharFloatAmount"];
				return o == null ? 3 : (int)o;
			}
			set
			{
				ViewState["CharFloatAmount"] = value;
			}
		}

		/// <summary>
		/// MD5函数
		/// </summary>
		/// <param name="str">原始字符串</param>
		/// <returns>MD5结果</returns>
		public static string MD5(string str)
		{
			byte[] b = Encoding.Default.GetBytes(str);
			b = new MD5CryptoServiceProvider().ComputeHash(b);
			string ret = "";
			for (int i = 0; i < b.Length; i++)
				ret += b[i].ToString("x").PadLeft(2, '0');
			return ret;
		}

		/// <summary>
		/// 取得当前生成的验证码的值
		/// </summary>
		private static string Value
		{
			get
			{
				HttpCookieCollection cookies = HttpContext.Current.Request.Cookies;
				if (cookies == null || cookies[StorageKey] == null)
					return null;

				return cookies[StorageKey].Value;
			}
			set
			{
				if (string.IsNullOrEmpty(value)) return;
				HttpContext.Current.Response.Cookies[StorageKey].Value = value;
			}
		}

		/// <summary>
		/// 编码生成的验证码。
		/// </summary>
		/// <param name="value">进行编码的验证码。</param>
		/// <returns>编码后的验证码。</returns>
		protected static string EncodeCheckCode(string value)
		{
			string ip = HttpContext.Current.Request.UserHostAddress;
			return MD5(MD5(value.ToLower() + ip));
		}

		/// <summary>
		/// 验证输入的验证码是否通过。
		/// </summary>
		/// <param name="code">进行验证的验证码。</param>
		/// <returns>如果验证通过，则返回 true，否则返回 false。</returns>
		public static bool Validate(string code)
		{
			string value = Value;

			if (string.IsNullOrEmpty(code) || string.IsNullOrEmpty(value))
				return false;
			return (EncodeCheckCode(code) == value);
		}

		/// <summary> 
		/// 将此控件呈现给指定的输出参数。
		/// </summary>
		/// <param name="output"> 要写出到的 HTML 编写器 </param>
		protected override void Render(HtmlTextWriter output)
		{
			string checkCode = GenerateCheckCode();
			Value = EncodeCheckCode(checkCode);
			CreateCheckCodeImage(checkCode);
		}

		/// <summary>
		/// 生成验证码。
		/// </summary>
		/// <returns></returns>
		private string GenerateCheckCode()
		{
			/*int number;
			int number2;
			char code;
			string checkCode = String.Empty;

			System.Random random = new Random();

			for (int i = 0; i < CheckCodeLength; i++)
			{
				number = random.Next();
				number2 = random.Next();

				if (number % 2 == 0)
					code = (char)('0' + (char)(number2 % 10));
				else
					code = (char)('A' + (char)(number2 % 26));

				checkCode += code.ToString();
			}

			return checkCode;*/

			int number;
			char code;
			string checkCode = String.Empty;

			System.Random random = new Random();

			for (int i = 0; i < CheckCodeLength; i++)
			{
				number = random.Next();

				code = (char)('0' + (char)(number % 10));

				checkCode += code.ToString();
			}

			return checkCode;
		}

		/// <summary>
		/// 创建验证码图片。
		/// </summary>
		/// <param name="checkCode"></param>
		private void CreateCheckCodeImage(string checkCode)
		{
			if (string.IsNullOrEmpty(checkCode))
				return;

			Font font = GetFont();
			int? width = Width.HasValue ? (int?)Width.Value : null;
			int? height = Height.HasValue ? (int?)Height.Value : null;

			SizeF imgSize = MeasureString(checkCode, font);
			int sWidth = (int)Math.Ceiling(imgSize.Width);
			int charWidth = sWidth / checkCode.Length;
			if (width == null)
				width = sWidth + CharFloatAmount * 2;
			width = width.Value + (int)(charWidth * 0.5);
			if(height == null)
				height = (int)Math.Ceiling(imgSize.Height) + CharFloatAmount * 2;

			Bitmap image = new Bitmap(width.Value + Padding * 2, height.Value + Padding * 2);

			// 1. 生成随机背景颜色
			int nRed, nGreen, nBlue;  // 背景的三元色
			Random rd = new Random((int)DateTime.Now.Ticks);
			nRed = rd.Next(255) % 128 + 128;
			nGreen = rd.Next(255) % 128 + 128;
			nBlue = rd.Next(255) % 128 + 128;

			// 2. 填充位图背景
			Graphics g = Graphics.FromImage(image);
			g.FillRectangle(new SolidBrush(Color.FromArgb(nRed, nGreen, nBlue))
				 , 0
				 , 0
				 , width.Value
				 , height.Value);

			// 3. 绘制干扰线条，采用比背景略深一些的颜色
			int nLines = 3;
			Pen pen = new Pen(Color.FromArgb(nRed - 30, nGreen - 30, nBlue - 30), 2);
			for (int a = 0; a < nLines; a++)
			{
				int x1 = rd.Next() % width.Value;
				int y1 = rd.Next() % height.Value;
				int x2 = rd.Next() % width.Value;
				int y2 = rd.Next() % height.Value;
				g.DrawLine(pen, x1, y1, x2, y2);
			}

			// 4. 循环取得字符，并绘制
			for (int i = 0; i < checkCode.Length; i++)
			{
				char c = checkCode[i];
				int x = (Padding + i * charWidth + rd.Next(CharFloatAmount * 2));
				int y = Padding + rd.Next(CharFloatAmount * 2);

				// 绘制字符
				g.DrawString(c.ToString(),
					font,
					new SolidBrush(Color.FromArgb(nRed - 100 + y * 3, nGreen - 100 + y * 3, nBlue - 100 + y * 3)),
					x,
					y);
			}

			// 5. 输出字节流
			//image = TwistImage(image, true, font.Size / 5, rd.NextDouble() * PI2);
			//image = TwistImage(image, false, font.Size / 5, rd.NextDouble() * PI2);

			System.IO.MemoryStream ms = new System.IO.MemoryStream();
			image.Save(ms, System.Drawing.Imaging.ImageFormat.Jpeg);
			image.Dispose();
			g.Dispose();

			HttpContext.Current.Response.ClearContent();
			HttpContext.Current.Response.ContentType = "image/jpeg";
			HttpContext.Current.Response.BinaryWrite(ms.ToArray());
		}

		/// <summary>
		/// 返回字体。
		/// </summary>
		/// <returns></returns>
		protected Font GetFont()
		{
			return Font;
		}

		/// <summary>
		/// 测量字符串的尺寸。
		/// </summary>
		/// <param name="text">进行测量的字符串。</param>
		/// <param name="font">字符串的字体设置。</param>
		/// <returns>测量的结果，以 SizeF 类型反回。</returns>
		protected SizeF MeasureString(string text, Font font)
		{
			Bitmap image = new Bitmap(1, 1);
			Graphics g = Graphics.FromImage(image);
			return g.MeasureString(text, font);
		}





		private const double PI = 3.1415926535897932384626433832795;
		private const double PI2 = 6.283185307179586476925286766559;

		/// <summary>
		/// 正弦曲线Wave扭曲图片
		/// </summary>
		/// <param name=srcBmp></param>
		/// <param name=bXDir></param>
		/// <param name=nMultValue>波形的幅度倍数</param>
		/// <param name=dPhase>波形的起始相位,取值区间[0-2*PI)</param>
		/// <returns></returns>
		public System.Drawing.Bitmap TwistImage(Bitmap srcBmp, bool bXDir, double dMultValue, double dPhase)
		{
			System.Drawing.Bitmap destBmp = new Bitmap(srcBmp.Width, srcBmp.Height);

			// 将位图背景填充为白色
			System.Drawing.Graphics graph = System.Drawing.Graphics.FromImage(destBmp);
			graph.FillRectangle(new SolidBrush(System.Drawing.Color.White), 0, 0, destBmp.Width, destBmp.Height);
			graph.Dispose();

			double dBaseAxisLen = bXDir ? (double)destBmp.Height : (double)destBmp.Width;

			for (int i = 0; i < destBmp.Width; i++)
			{
				for (int j = 0; j < destBmp.Height; j++)
				{
					double dx = 0;
					dx = bXDir ? (PI2 * (double)j) / dBaseAxisLen : (PI2 * (double)i) / dBaseAxisLen;
					dx += dPhase;
					double dy = Math.Sin(dx);

					// 取得当前点的颜色
					int nOldX = 0, nOldY = 0;
					nOldX = bXDir ? i + (int)(dy * dMultValue) : i;
					nOldY = bXDir ? j : j + (int)(dy * dMultValue);

					System.Drawing.Color color = srcBmp.GetPixel(i, j);
					if (nOldX >= 0 && nOldX < destBmp.Width
					 && nOldY >= 0 && nOldY < destBmp.Height)
					{
						destBmp.SetPixel(nOldX, nOldY, color);
					}
				}
			}

			return destBmp;
		}
	}
}
